/**
 * @fileoverview View logs. Retrieve logs from server if needed.
 */

/**
 * Colors used to show different mouse events.
 */
var Colors = {
  CLICK: '#FF0000',
  DRAG: '#999999',
  MOVE: '#CCCCCC'
};

/**
 * In pixels, to identify a mouse location.
 */
var MOUSE_ERROR = 5;

/**
 * Server URL.
 */
var SERVER = 'http://trail.lri.fr/show?type=json';
//var SERVER = 'http://wikipediaviz.lri.fr/cgi-bin/show.cgi';

/**
 * Category label to get the mouse events.
 */
var STUDY_CAT = 'vizattstudy';

/**
 * Show the logs. Expected log format:
 * userId, timestamp, storyIndex, conditionIndex, x, y, mouseEventType,
 * useragent
 */
function viewLogs(opt_logs) {
  if (!opt_logs) {
    getLogsFromServer();
    return;
  }
  
  logs = opt_logs;
  if (!logs.ppts || logs.ppts.length == 0) {
    displayMsg('No logs to display');
    return;
  }

  // Shows the summary.
  document.getElementById('logSummary').innerHTML = 
      JSON.stringify(logs.logSummary);

  // Views the first logs automatically.
  viewPptLogs(logs.ppts[0]);

  pptSelect = document.getElementById('pptSelect');
  if (!pptSelect) {
    return;
  }
  pptSelect.onchange = onSelectPpt;
  logs.ppts.forEach(function(ppt) {
    pptSelect.options[pptSelect.options.length] = new Option(ppt, ppt);
  });
}

/**
 * View the selected participant logs.
 */
function onSelectPpt() {
  clearPage(stage);
  viewPptLogs(pptSelect.options[pptSelect.selectedIndex].text);
}

function viewPptLogs(pptId) {
  viewPptMouseLog(logs.mouseLogs[pptId]);
  viewPptLogAsTable(logs.pageLogs[pptId], 'pageLogs');
  viewPptLogAsTable(logs.otherLogs[pptId], 'otherLogs');
}

function viewPptLogAsTable(pptLogs, elementId) {
  var table = '<table>';
  for (var i = 0; i < pptLogs.length; i++) {
    table += '<tr>';
    var pptLog = pptLogs[i];

    for (var key in pptLog) {
      table += '<td>' + pptLog[key] + '</td>';
     }
     table += '</tr>';
  }
  table += '</table>';
  var logs = document.getElementById(elementId);
  logs.innerHTML = table;
}

/**
 * @param {Array.<Object>} pptLogs To display.
 */
function viewPptMouseLog(pptLogs) {
  if (!pptLogs || pptLogs.length == 0) {
    displayMsg('No logs to display for ppt');
    return;
  }

  storyIndex = pptLogs[0].storyIndex;
  conditionIndex = pptLogs[0].conditionIndex,

  stage = document.getElementById('stage');
  stage.appendChild(createStory(pptLogs[0].lastSelectedRow));

  // Can only draw the chart when it has been added to an existing element,
  // or Gviz will return an error message 'Your browser does not support
  // charts'.
  storyIndex = pptLogs[0].storyIndex;
  drawChart();

  // Adds a pane on top to draw the mouse paths.
  var canvas = document.createElement('canvas');
  stage.insertBefore(canvas, stage.childNodes[0]);
  canvas.width = stage.offsetWidth;
  canvas.height = stage.offsetHeight;
  var context = canvas.getContext('2d');

  var marker = document.createElement('div');
  marker.id = 'marker';
  stage.appendChild(marker);

  canvas.addEventListener('mousemove',
      showLogDetails.bind(showLogDetails, pptLogs, marker), false);

  // Figures out the offsets.
  showMouseEvents(context, pptLogs);

  // Shows the user agent.
  document.getElementById('userAgent').innerHTML = pptLogs[0].useragent;
}

/**
 * @param {string} msg To display.
 */
function displayMsg(msg) {
  var annotation = document.getElementById('annotation');
  annotation.innerHTML = msg;
}

function getLogsFromServer() {
  displayMsg('Retrieving logs...');
  var request = new XMLHttpRequest();
  request.open('GET', SERVER, true);

  request.onreadystatechange = function() {
    if (request.readyState == 4 && request.status === 200) {
      displayMsg("Received logs");
      viewLogs(parseLog(JSON.parse(request.responseText)));
    } else {
      viewLogs({mouseLogs: {}, timeLogs: {}, ppts: []});
    }
  }
  request.send();
}

/**
 * Filters and parses the JSON-encoded strings.
 * @param {Array.<string>} rawLogs Array with string of format:
 *   [session, ts, cat, action, label] and label is a JSON-encoded string
 *   of with fields storyIndex, conditionIndex, timestamp, x, y, type, useragent.
 * @return {{mouseLogs: {string, Array.<Object>}, ppts: Array.<string>,
 *     {timeLogs: {string, Array.<Array>}} 
 *   Participants are identified by their session id stored in the "session" 
 *   field. The returned logs object is keyed by this id with an array of 
 *   mouse events with the same field as the JSON-encoded string in the "label"
 *   field. 
 */
function parseLog(rawLogs) {
  displayMsg('Parsing ' + rawLogs.length + ' records...');
  var pageLogs = {};
  var mouseLogs = {};
  var otherLogs = {};
  var logSummary = {total: 0, finished: 0};
  var ppts = [];
  var qaTs = 0;

  // Gets the field indices.
  var pptIdIndex = rawLogs[0].indexOf('session');
  var tsIndex = rawLogs[0].indexOf('ts');
  var catIndex = rawLogs[0].indexOf('cat');
  var actionIndex = rawLogs[0].indexOf('action');
  var labelIndex = rawLogs[0].indexOf('label');

  for (var i = rawLogs.length - 1; i >= 1; i--) {
    // Only parse in records with "cat" == "vizattstudy".
    var rawLog = rawLogs[i];
    if (rawLog[catIndex] != STUDY_CAT) {
      continue;
    } 
    
    // Add the participants into the various logs.
    var ppt = rawLog[pptIdIndex];
    if (!mouseLogs[ppt]) {
       mouseLogs[ppt] = [];
       pageLogs[ppt] = [];
       otherLogs[ppt] = [];
       ppts.push(ppt);
    }          


    // Add the logs.
    // The label field has the complete json for mouse events.
    var action = rawLog[actionIndex];
    if (action == LogEvents.PAGE) {
      if (pageLogs[ppt].length == 0) {
        logSummary.total += 1;
      } else if (rawLog[labelIndex] == '"LP-Instruction"') {
        logSummary.finished += 1;
      } else if (rawLog[labelIndex] == '"S"') {
        qaTs = rawLog[tsIndex];
      }

      pageLogs[ppt].push({
          ts: rawLog[tsIndex],
          page: rawLog[labelIndex]}); 
    } else if (action == LogEvents.QA_MOUSE) {
      mouseLogs[ppt].push(JSON.parse(rawLog[labelIndex]));
    } else if (action == LogEvents.CHART || action == LogEvents.GRAPHIC) {
      var value = JSON.parse(rawLog[labelIndex]);
      value['type'] = action;
      value['ts'] = rawLog[tsIndex] - qaTs;
      otherLogs[ppt].push(value);
    }
  }

  // Sorts the mouse logs by time.
  var validPpts = [];
  for (var i = 0; i < ppts.length; i++) {
    var ppt = ppts[i];
    if (mouseLogs[ppt].length > 1) {
      mouseLogs[ppt] = sortLogsByTimestamp(mouseLogs[ppt]);
      pageLogs[ppt] = calculateDuration(pageLogs[ppt]);
      validPpts.push(ppt);
    }
  }

  displayMsg('');
  return {mouseLogs: mouseLogs, ppts: validPpts, pageLogs: pageLogs,
          otherLogs: otherLogs, logSummary: logSummary};
}

function sortLogsByTimestamp(logs) {
  logs.sort(function(a, b) {
    return a.timestamp - b.timestamp;
  });
  return logs;
}

function calculateDuration(logs) {
  for (var i = 0; i < logs.length - 1; i++) {
    logs[i].duration = logs[i + 1].ts - logs[i].ts;
    delete logs[i].ts;
  }
  delete logs[logs.length - 1];
  return logs;
}

/**
 * Shows the logged mouse event.
 * @param {Context} context The canvas context object.
 * @param {Array.<Object>} logs. See logger.js for details.
 */
function showMouseEvents(context, logs) {
  context.strokeStyle = Colors.MOVE;  // Default stroke color.
  context.lineWidth = 3;

  for (var i = 0; i < logs.length; i++) {
    var record = logs[i];
    var nextRecord = i < logs.length ? logs[i + 1] : null;

    if (record.type == 'click' || record.type == 'dbclick') {
      drawClick(context, record);
    } else if (record.type == 'mousedown' || record.type == 'mouseup') {
      drawMouseUpDown(context, record, record.type == 'mousedown');
    } else if (nextRecord && record.type == 'mousemove') {
      drawMouseMove(context, record, nextRecord);
    }
  }
}

/**
 * Locates and shows the log record.
 * @param {Array.<Object>} logs. See logger.js for details.
 * @param {Element} marker Div element to visually show the (x,y) coordinates
 *    of the logged mouse event.
 * @param {Event} evt The mousemove event on the canvas.
 */
function showLogDetails(logs, marker, evt) {
  var annotation = document.getElementById('annotation');
  // finds the logs based on x/y position
  for (var i = 0; i < logs.length; i++) {
    var log = logs[i];
    if (Math.abs(log.x - evt.offsetX) < MOUSE_ERROR &&
        Math.abs(log.y - evt.offsetY) < MOUSE_ERROR) {
      annotation.innerHTML = JSON.stringify(log);
      markPoint(marker, log.x, log.y);
    }
  }
}

/**
 * Draws to indicate a 'click' event.
 * @param {Context} context The canvas context object.
 * @param {Object} record See logger.js for details.
 */
function drawClick(context, record) {
  context.beginPath();
  context.arc(record.x, record.y, 2, 0, Math.PI*2, true);
  context.closePath();
  context.fillStyle = Colors.CLICK;
  context.fill();
}

/**
 * Draws to indicate a 'click' event.
 * @param {Context} context The canvas context object.
 * @param {Object} record See logger.js for details.
 * @param {boolean} down True if it is a mouse down event.
 */
function drawMouseUpDown(context, record, down) {
  context.strokeStyle = down ? Colors.DRAG : Colors.MOVE;
  if (down) {
    context.fillStyle = Colors.DRAG;
  }
  context.beginPath();
  context.arc(record.x, record.y, 2, 0, Math.PI*2, true);
  context.closePath();
  context.stroke();

  if (down) {
    context.fill();
  }
}

/**
 * Draws to indicate a 'mousemove' event.
 * @param {Context} context The canvas context object.
 * @param {Object} record See logger.js for details.
 * @param {Object} nextRecord See logger.js for details.
 */
function drawMouseMove(context, record, nextRecord) {
  // Stroke color determined at mouse up / down.
  context.beginPath();
  context.moveTo(record.x, record.y);
  context.lineTo(nextRecord.x, nextRecord.y);
  context.closePath();
  context.stroke();
}

/**
 * Places the marker element at (x,y).
 * @param {Element} marker Div element to visually show the (x,y) coordinates
 *    of the logged mouse event.
 * @param {number} x coordinate, in pixel.
 * @param {number} y coordinate, in pixel.
 */
function markPoint(marker, x, y) {
  marker.style.left = (x - 0.5 * marker.offsetWidth) + 'px';
  marker.style.top = (y - 0.5 * marker.offsetHeight) + 'px';
}

/**
 * Removes all children of input node.
 * @param {Element} node
 */
function clearPage(node) {
  if (!node) {
    return;
  }
  var child;
  while ((child = node.firstChild)) {
    node.removeChild(child);
  }
}

